﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GenericGA.reader;
using GenericGA.extrandom;

namespace GenericGA.generationmaker
{
    /// <summary>
    /// Private class for the binary chromosome of an individual.
    /// </summary>
    class Chromosome
    {

        /// <summary>
        /// The bits of the binary chromosome.
        /// </summary>
        private readonly List<ulong> bits;

        /// <summary>
        /// Getter for the bits of the binary chromosome. Returns a copy to keep the chromosome immutable.
        /// </summary>
        public List<ulong> Bits
        {
            get
            {
                return new List<ulong>(bits);
            }
        }

        /// <summary>
        /// A random number generator.
        /// </summary>
        private readonly ExtRandom randGen;

        /// <summary>
        /// Create a chromosome based on an initial sequence of bits and a random number generator.
        /// </summary>
        /// <param name="randGen">The random number generator to use to add more random bits when needed.</param>
        /// <param name="bits">The initial sequence of bits.</param>
        public Chromosome(ExtRandom randGen, List<ulong> bits)
        {
            this.randGen = randGen;
            this.bits = bits;
        }

        /// <summary>
        /// Get the bit length of the chromosome.
        /// </summary>
        /// <returns>The bit length.</returns>
        public int BitLength()
        {
            return bits.Count * 64;
        }

        /// <summary>
        /// Get a reader object for this chromosome. There can be multiple readers for the same chromosome.
        /// </summary>
        /// <returns>A reader object.</returns>
        public Reader GetReader()
        {
            return new Reader(new BitsEnumerable(randGen, bits).GetEnumerable());
        }

        /// <summary>
        /// Enumerable class to provide an iterator over the sequence of bits, thereby allowing multiple iterations for the same chromosome.
        /// </summary>
        private class BitsEnumerable
        {
            
            /// <summary>
            /// The bits of the binary chromosome.
            /// </summary>
            private readonly List<ulong> bits;

            /// <summary>
            /// A random number generator.
            /// </summary>
            private readonly ExtRandom randGen;

            /// <summary>
            /// Create a chromosome based on an initial sequence of bits and a random number generator.
            /// </summary>
            /// <param name="randGen">The random number generator to use to add more random bits when needed.</param>
            /// <param name="bits">The initial sequence of bits.</param>
            public BitsEnumerable(ExtRandom randGen, List<ulong> bits)
            {
                this.randGen = randGen;
                this.bits = bits;
            }

            /// <summary>
            /// Get an iteration over the bits of the chromosome.
            /// </summary>
            /// <returns>An enumerator which supplies an infinite amount of bits taken from the initial bit sequence of the chromosome, adding random bits to the chromosome when more are requested.</returns>
            public System.Collections.Generic.IEnumerable<bool> GetEnumerable()
            {
                int index = 0;
                while (true)
                {
                    //If more bits are needed than available, add more random bits.
                    if (index == bits.Count)
                    {
                        bits.Add(randGen.NextULong());
                    }

                    ulong mask = 0x8000000000000000; //First bit only is set
                    ulong currBits = bits[index];

                    while (mask != 0) //At the end the mask bit will be shifted out
                    {
                        bool bit = (currBits & mask) != 0;
                        yield return bit; //Return if the current bits have a set bit where the mask bit currently is
                        mask = mask >> 1; //Move the set bit left
                    }
                    index++;
                }
            }

        }

    }
}
